package distributedbank;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

import distributedbank.server.messages.OracleKill;
import distributedbank.server.messages.OracleNotification;

/**
 * Oracle is the failure detector. It keeps one list of servers for each bank
 * branch. It can destroy a server or recover a destroyed server. It notify all
 * servers when some server is bad.
 */
public class Oracle implements ActionListener {
    private static int width = 600;

    private static int height = 300;

    private final List<Set<Integer>> serverStatus = new ArrayList<Set<Integer>>();

    private final JFrame frame;

    private final JLabel branch1Label;

    private final JLabel branch2Label;

    private final JLabel branch3Label;

    private final JLabel branch4Label;

    private final JLabel branchServerLabel[][] = new JLabel[4][3];

    // enable/disable, branch, servers
    private final JButton serverControl[][][] = new JButton[2][4][3];

    // a disabled counter for each branch, cannot exceed 2
    private final int disabledCounter[] = new int[4];

    private final int disabledServer[] = new int[4];

    private final int maxServerId[] = new int[] { 3, 3, 3, 3 };

    private final JPanel branch1Panel;

    private final JPanel branch2Panel;

    private final JPanel branch3Panel;

    private final JPanel branch4Panel;

    private final JPanel branch1ServerPanel;

    private final JPanel branch2ServerPanel;

    private final JPanel branch3ServerPanel;

    private final JPanel branch4ServerPanel;

    @SuppressWarnings("serial")
    public Oracle() {
        for (int i = 0; i < 4; i++) {
            serverStatus.add(new TreeSet<Integer>());
            for (int j = 0; j < 3; j++) {
                serverStatus.get(i).add(j + 1);
            }
        }

        frame = new JFrame("ORACLE");
        branch1Label = new JLabel("Branch 1");
        branch2Label = new JLabel("Branch 2");
        branch3Label = new JLabel("Branch 3");
        branch4Label = new JLabel("Branch 4");
        branch1Label.setHorizontalAlignment(JLabel.CENTER);
        branch2Label.setHorizontalAlignment(JLabel.CENTER);
        branch3Label.setHorizontalAlignment(JLabel.CENTER);
        branch4Label.setHorizontalAlignment(JLabel.CENTER);

        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 3; j++) {
                branchServerLabel[i][j] = new JLabel("Server " + (j + 1));
                branchServerLabel[i][j].setHorizontalAlignment(JLabel.CENTER);
            }
        }

        // 0 for disabled, 1 for enabled buttons
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 3; k++) {
                    final int branchId = j;
                    final int serverId = k;
                    if (i == 0) {
                        serverControl[i][j][k] = new JButton(
                                new AbstractAction("Disable") {
                                    public final void actionPerformed(
                                            ActionEvent e) {
                                        disablePressed(branchId, serverId);
                                    }
                                });
                        serverControl[i][j][k].setText("Disable");
                    } else {
                        serverControl[i][j][k] = new JButton(
                                new AbstractAction("Enable") {
                                    public final void actionPerformed(
                                            ActionEvent e) {
                                        enablePressed(branchId, serverId);
                                    }
                                });
                        serverControl[i][j][k].setText("Enable");
                        // initially, all servers are enabled, set those buttons
                        // for false
                        serverControl[i][j][k].setEnabled(false);
                    }
                }
            }
        }

        branch1Panel = new JPanel();
        branch2Panel = new JPanel();
        branch3Panel = new JPanel();
        branch4Panel = new JPanel();

        branch1ServerPanel = new JPanel();
        branch2ServerPanel = new JPanel();
        branch3ServerPanel = new JPanel();
        branch4ServerPanel = new JPanel();

        frame.setSize(width, height);
        frame.setVisible(true);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new GridLayout(2, 2));
        frame.add(branch1Panel);
        frame.add(branch2Panel);
        frame.add(branch3Panel);
        frame.add(branch4Panel);

        branch1Panel.setLayout(new BorderLayout());
        branch1Panel.add("North", branch1Label);
        branch1Panel.add(branch1ServerPanel);
        branch1ServerPanel.setLayout(new GridLayout(3, 3));

        branch1ServerPanel.add(branchServerLabel[0][0]);
        branch1ServerPanel.add(serverControl[1][0][0]);
        branch1ServerPanel.add(serverControl[0][0][0]);
        branch1ServerPanel.add(branchServerLabel[0][1]);
        branch1ServerPanel.add(serverControl[1][0][1]);
        branch1ServerPanel.add(serverControl[0][0][1]);
        branch1ServerPanel.add(branchServerLabel[0][2]);
        branch1ServerPanel.add(serverControl[1][0][2]);
        branch1ServerPanel.add(serverControl[0][0][2]);

        branch2Panel.setLayout(new BorderLayout());
        branch2Panel.add("North", branch2Label);
        branch2Panel.add(branch2ServerPanel);
        branch2ServerPanel.setLayout(new GridLayout(3, 3));
        branch2ServerPanel.add(branchServerLabel[1][0]);
        branch2ServerPanel.add(serverControl[1][1][0]);
        branch2ServerPanel.add(serverControl[0][1][0]);
        branch2ServerPanel.add(branchServerLabel[1][1]);
        branch2ServerPanel.add(serverControl[1][1][1]);
        branch2ServerPanel.add(serverControl[0][1][1]);
        branch2ServerPanel.add(branchServerLabel[1][2]);
        branch2ServerPanel.add(serverControl[1][1][2]);
        branch2ServerPanel.add(serverControl[0][1][2]);

        branch3Panel.setLayout(new BorderLayout());
        branch3Panel.add("North", branch3Label);
        branch3Panel.add(branch3ServerPanel);
        branch3ServerPanel.setLayout(new GridLayout(3, 3));
        branch3ServerPanel.add(branchServerLabel[2][0]);
        branch3ServerPanel.add(serverControl[1][2][0]);
        branch3ServerPanel.add(serverControl[0][2][0]);
        branch3ServerPanel.add(branchServerLabel[2][1]);
        branch3ServerPanel.add(serverControl[1][2][1]);
        branch3ServerPanel.add(serverControl[0][2][1]);
        branch3ServerPanel.add(branchServerLabel[2][2]);
        branch3ServerPanel.add(serverControl[1][2][2]);
        branch3ServerPanel.add(serverControl[0][2][2]);

        branch4Panel.setLayout(new BorderLayout());
        branch4Panel.add("North", branch4Label);
        branch4Panel.add(branch4ServerPanel);
        branch4ServerPanel.setLayout(new GridLayout(3, 3));
        branch4ServerPanel.add(branchServerLabel[3][0]);
        branch4ServerPanel.add(serverControl[1][3][0]);
        branch4ServerPanel.add(serverControl[0][3][0]);
        branch4ServerPanel.add(branchServerLabel[3][1]);
        branch4ServerPanel.add(serverControl[1][3][1]);
        branch4ServerPanel.add(serverControl[0][3][1]);
        branch4ServerPanel.add(branchServerLabel[3][2]);
        branch4ServerPanel.add(serverControl[1][3][2]);
        branch4ServerPanel.add(serverControl[0][3][2]);

        frame.show();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        this.sendOracleBroadcast();
    }

    private void enablePressed(int branchId, int serverId) {
        if (disabledCounter[branchId]-- == 2) {
            serverControl[0][branchId][disabledServer[branchId]]
                    .setEnabled(true);
        }
        serverControl[1][branchId][serverId].setEnabled(false);
        serverControl[0][branchId][serverId].setEnabled(true);
        // no need to check, it cannot go below 0

        String label = branchServerLabel[branchId][serverId].getText();
        int realServerId = Integer
                .valueOf(label.split(" ")[label.split(" ").length - 1]);

        serverStatus.get(branchId).add(realServerId);
        this.sendOracleBroadcast();
    }

    private void disablePressed(int branchId, int serverId) {
        serverControl[1][branchId][serverId].setEnabled(true);
        serverControl[0][branchId][serverId].setEnabled(false);

        String label = branchServerLabel[branchId][serverId].getText();
        int realServerId = Integer
                .valueOf(label.split(" ")[label.split(" ").length - 1]);
        maxServerId[branchId]++;
        branchServerLabel[branchId][serverId].setText("Server "
                + maxServerId[branchId]);

        if (++disabledCounter[branchId] == 2) {
            for (int k = 0; k < 3; k++)
                if (serverControl[0][branchId][k].isEnabled()) {
                    disabledServer[branchId] = k;
                    serverControl[0][branchId][k].setEnabled(false);
                    break;
                }
        }

        serverStatus.get(branchId).remove(realServerId);
        try {
            Wrapper.sendOracleMessage(new OracleKill(), Wrapper.basePort
                    + (branchId + 1) * 10 + realServerId);
        } catch (IOException e) {
            System.out.println("!Oracle Failed to send message to branch: "
                    + (branchId + 1) + " replica " + realServerId);
        }
        this.sendOracleBroadcast();
    }

    private void sendOracleBroadcast() {
        OracleNotification message = new OracleNotification(this.serverStatus);

        for (Integer branch : new int[] { 1, 2, 3, 4 }) {
            for (Integer server : new int[] { 0, 1, 2, 3 }) {
                int realServerId;
                if (server == 0) {
                    realServerId = 0;
                } else {
                    String label = branchServerLabel[branch - 1][server - 1]
                            .getText();
                    realServerId = Integer.valueOf(label.split(" ")[label
                            .split(" ").length - 1]);
                }
                int port = Wrapper.basePort + branch * 10 + realServerId;
                try {
                    Wrapper.sendOracleMessage(message, port);
                } catch (IOException e) {
                    System.out
                            .println("Oracle failed to send message to branch: "
                                    + branch + " replica " + server);
                }
            }
        }
    }

    public static void main(String[] args) {
        new Oracle();
    }

    @Override
    public void actionPerformed(ActionEvent e_) {
        // TODO Auto-generated method stub

    }
}
